{% load form_helpers %}
{% load helpers %}
{% load static %}

<section class="nb-drawer" tabindex="-1" id="{{ filter_form_name }}_drawer">
    <div class="nb-drawer-header">
        <h1>Filters</h1>
        <button type="button" class="btn-close" data-nb-dismiss="drawer" aria-label="Close"></button>
    </div>
    <div class="nb-drawer-body">
        <ul id="filter-tabs" class="nav nav-tabs">
            {% if filter_form %}
                <li role="presentation" class="nav-item">
                    <a class="nav-link{% if filter_form %} active{% endif %}"  href="#default-filter" role="tab" data-bs-toggle="tab">
                        Basic
                    </a>
                </li>
            {% endif %}
            {% if dynamic_filter_form %}
                <li role="presentation" class="nav-item">
                    <a class="nav-link position-relative{% if not filter_form %} active{% endif %}" href="#advanced-filter" role="tab" data-bs-toggle="tab">
                        Advanced
                        {% advanced_filter_indicator basic_filter_form=filter_form filter_params=filter_params %}
                    </a>
                </li>
            {% endif %}
        </ul>
        <div class="tab-content h-100">
            {% if filter_form %}
                <div id="default-filter" role="tabpanel" class="tab-pane fade{% if filter_form %} show active{% endif %} h-100 p-20">
                    {% include 'inc/search_panel.html' %}
                </div>
            {% endif %}
            {% if dynamic_filter_form %}
                <div id="advanced-filter" role="tabpanel" class="tab-pane fade{% if not filter_form %} show active{% endif %} h-100 p-20">
                    <form class="h-100 vstack" id="dynamic-filter-form">
                        <div class="align-content-start flex-fill pb-20">
                            {{ dynamic_filter_form.management_form }}
                            {% for field in dynamic_filter_form.0.visible_fields %}
                                <div class="nb-form-group">
                                    <label for="{{ field.auto_id }}">{{ field.label|capfirst }}</label>
                                    {{ field }}
                                </div>
                            {% endfor %}
                            <button class="nb-dynamic-filter-add btn btn-primary d-block ms-auto my-20" type="button">
                                <span aria-hidden="true" class="mdi mdi-plus me-4"></span><!--
                                -->Add Filter
                            </button>
                            <hr class="border-top my-20">
                            <div class="nb-dynamic-filter-items d-flex flex-wrap gap-10">
                                {% if filter_params %}
                                    {% for field in filter_params %}
                                        <span class="badge nb-multi-badge" data-nb-field="{{ field.name }}">
                                            <button class="nb-dynamic-filter-remove" type="button">
                                                <span aria-hidden="true" class="mdi mdi-close"></span>
                                                <span class="visually-hidden">Remove All</span>
                                            </button>
                                            {{ field.display }}:
                                            <span class="nb-multi-badge-items">
                                                {% for value in field.values %}
                                                    <span class="badge" data-nb-value="{{ value.name }}"><!--
                                                        --><button class="nb-dynamic-filter-remove" type="button">
                                                        <span aria-hidden="true" class="mdi mdi-close"></span>
                                                        <span class="visually-hidden">Remove</span>
                                                    </button><!--
                                                        -->{{ value.display }}<!--
                                                        --><input name="{{ field.name }}" type="hidden" value="{{ value.name }}">
                                                    </span>
                                                {% endfor %}
                                            </span>
                                        </span>
                                    {% endfor %}
                                {% endif %}
                            </div>
                        </div>

                        <div class="d-print-none justify-content-between mb-n20 nb-form-sticky-footer">
                            <button type="submit" class="btn btn-primary">
                                <span class="mdi mdi-check me-4" aria-hidden="true"></span><!--
                                -->Apply Specified
                            </button>
                            <button type="reset" class="btn btn-secondary">
                                <span aria-hidden="true" class="mdi mdi-close me-4"></span><!--
                                -->Clear All
                            </button>
                        </div>
                    </form>
                </div>
            {% endif %}
        </div>
    </div>
</section>

{% block javascript %}
    <script>
        (() => {
            document.addEventListener('DOMContentLoaded', () => {
                const NON_FILTER_PARAMS = [
                    'all_filters_removed',
                    'clear_view',
                    'per_page',
                    'saved_view',
                    'sort',
                    'table_changes_pending',
                ];

                /**
                 * Insert given `element` as `parent`'s first child.
                 * @param {HTMLElement} element - Element to be inserted.
                 * @param {HTMLElement} parent - Parent container for given `element`.
                 * @returns {void} Do not return any value, modify DOM in-place.
                 */
                const insertFirst = (element, parent) => {
                    parent.hasChildNodes() ? parent.insertBefore(element, parent.firstElementChild) : parent.appendChild(element);
                };

                /**
                 * Get HTML elements for default and dynamic filter forms.
                 * @returns {object} Object with `defaultFilterForm` and `dynamicFilterForm` HTML elements or `null`,
                 *   depending on their actual existence in the DOM.
                 */
                const getFilterForms = () => ({
                    defaultFilterForm: document.querySelector('#default-filter form'),
                    dynamicFilterForm: document.querySelector('#dynamic-filter-form'),
                });

                const prepopulateFilterSelectsFromURL = (() => {
                    const { defaultFilterForm } = getFilterForms();
                    const urlParams = new URLSearchParams(window.location.search);
                    // Only target Select2 controls inside the default filter form.
                    [...defaultFilterForm.querySelectorAll('select.nautobot-select2-multi-value-char')].forEach((el) => {
                        const name = el.getAttribute('name');
                        if (!name) { return; }
                        const values = urlParams.getAll(name);
                        if (!values.length) { return; }
                        values.forEach((val) => {
                            let found = Array.prototype.find.call(el.options, (opt) => {
                                return String(opt.value) === String(val);
                            });
                            if (found) {
                                found.selected = true;
                            } else {
                                el.add(new Option(val, val, true, true));
                            }
                        });
                        if (window.jQuery && $(el).data('select2')) {
                            $(el).trigger('change');
                        }
                    });
                })();

                /**
                 * Synchronize given `filter` value from default to dynamic filter form.
                 * @param {string} name - Filter name.
                 * @returns {void} Do not return any value, just set given field values.
                 */
                const syncDefaultToDynamic = (name) => {
                    const { defaultFilterForm } = getFilterForms();

                    const field = defaultFilterForm.querySelector(`[name="${name}"]`);
                    const text = field?.closest('.nb-form-group')?.querySelector('label')?.innerText?.trim();
                    /*
                     * `manageDynamicFilter` takes an array of `{ text: string, value: string }` objects as `value`, so
                     * regardless of field being a textbox, a single or multiple choice combobox, conversion is required:
                     *   1. For combobox (i.e. `<select>`), iterate over selected options and map them to display `text`
                     *     and underlying `value`.
                     *   2. For textbox (i.e. `<input>` or `<textarea>`):
                     *     1. Use entered value as both display `text` and underlying `value`.
                     *     2. If field is empty, use an empty array.
                     */
                    const value =
                        field?.tagName === 'SELECT'
                        ? [...field.selectedOptions].map((option) => ({ text: option.innerText, value: option.value }))
                        : [...(field?.value ? [{ text: field.value, value: field.value }] : [])];

                    manageDynamicFilter({ action: 'set', name, text, value });
                };

                /**
                 * Synchronize given `filter` value from dynamic to default filter form.
                 * @param {string} name - Filter name.
                 * @returns {void} Do not return any value, just set given field values.
                 */
                const syncDynamicToDefault = (name) => {
                    const { defaultFilterForm, dynamicFilterForm } = getFilterForms();

                    const current = [...dynamicFilterForm.querySelectorAll(`input[name="${name}"][type="hidden"]`)].map(
                        (input) => ({
                            text: [...(input.parentElement?.childNodes ?? [])].find((node) => node.nodeName === '#text')?.wholeText,
                            value: input.getAttribute('value'),
                        }),
                    );

                    const sync = defaultFilterForm?.querySelector(`[name=${name}]`);

                    if (sync?.tagName === 'INPUT' || sync?.tagName === 'TEXTAREA') {
                        sync.value = current.length > 0 ? current[0].value : '';
                    } else if (sync?.tagName === 'SELECT') {
                        window.nb.select2.setSelect2Value(sync, current);
                    }
                };

                /**
                 * Manage dynamic filter either by adding values to the existing selection or by setting passed values
                 * regardless of previous selection.
                 * @param {'add'|'set'} action - Use `'add'` to add given value to the existing selection; use `'set'`
                 *   to set given value regardless of what was set previously.
                 * @param {string} name - Filter field name.
                 * @param {string} text - Filter display text.
                 * @param {object[]} value - Array of objects containing `text` and `value` key-value pairs.
                 */
                const manageDynamicFilter = ({ action, name, text, value }) => {
                    const { dynamicFilterForm } = getFilterForms();
                    const items = dynamicFilterForm.querySelector('.nb-dynamic-filter-items');

                    const group = (() => {
                        const existing = items.querySelector(`.nb-multi-badge[data-nb-field="${name}"]`);
                        if (existing) {
                            return existing;
                        }

                        const group = document.createElement('span');
                        group.classList.toggle('badge', true);
                        group.classList.toggle('nb-multi-badge', true);
                        group.setAttribute('data-nb-field', name);

                        const button = document.createElement('button');
                        button.classList.toggle('nb-dynamic-filter-remove', true);
                        button.setAttribute('type', 'button');

                        const close = document.createElement('span');
                        close.classList.toggle('mdi', true);
                        close.classList.toggle('mdi-close', true);
                        close.setAttribute('aria-hidden', 'true');

                        const remove = document.createElement('span');
                        remove.classList.toggle('visually-hidden', true);
                        remove.innerText = 'Remove All';

                        const children = document.createElement('span');
                        children.classList.toggle('nb-multi-badge-items', true);

                        button.appendChild(close);
                        button.appendChild(remove);
                        group.appendChild(button);
                        group.insertAdjacentText('beforeend', `${text}:`);
                        group.appendChild(children);

                        return group;
                    })();

                    const children = group.querySelector('.nb-multi-badge-items');
                    const existing = [...children.querySelectorAll('.badge[data-nb-value]')];

                    /*
                     * Depending on `action` remove the following badges:
                     *   1. For `'add'`, remove already existing filters with the same key-value pairs and re-create them later.
                     *   2. For `'set'`, remove all existing filters of given key and apply only these passed as `values`.
                     */
                    const badgesToRemove =
                        action === 'add'
                        ? existing.filter((badge) => value.some((item) => badge.getAttribute('data-nb-value') === item.value))
                        : existing;
                    badgesToRemove.forEach((item) => children.removeChild(item));

                    value.forEach(({ text, value }) => {
                        const badge = document.createElement('span');
                        badge.classList.toggle('badge', true);
                        badge.setAttribute('data-nb-value', value);

                        const input = document.createElement('input');
                        input.setAttribute('name', name);
                        input.setAttribute('type', 'hidden');
                        input.setAttribute('value', value);

                        const button = document.createElement('button');
                        button.classList.toggle('nb-dynamic-filter-remove', true);
                        button.setAttribute('type', 'button');

                        const close = document.createElement('span');
                        close.classList.toggle('mdi', true);
                        close.classList.toggle('mdi-close', true);
                        close.setAttribute('aria-hidden', 'true');

                        const remove = document.createElement('span');
                        remove.classList.toggle('visually-hidden', true);
                        remove.innerText = 'Remove';

                        button.appendChild(close);
                        button.appendChild(remove);
                        badge.appendChild(button);
                        badge.insertAdjacentText('beforeend', text);
                        badge.appendChild(input);

                        insertFirst(badge, children);
                    });

                    // If group already exists, remove it and later insert as the first child.
                    if (items.contains(group)) {
                        items.removeChild(group);
                    }

                    if (children.hasChildNodes()) {
                        insertFirst(group, items);
                    }
                };

                document.addEventListener('click', (event) => {
                    const { defaultFilterForm, dynamicFilterForm } = getFilterForms();

                    const add = event.target.closest('button.nb-dynamic-filter-add');
                    if (add) {
                        const field = dynamicFilterForm.querySelector('[name$="lookup_field"]');
                        const type = dynamicFilterForm.querySelector('[name$="lookup_type"]');
                        const value = [...dynamicFilterForm.querySelectorAll('.nb-form-group')]
                            .pop()
                        ?.querySelector('input, select, textarea');

                        const isMissing = (control) =>
                        control?.selectedOptions ? control.selectedOptions.length === 0 : !control?.value;
                        if (isMissing(field) || isMissing(type) || isMissing(value)) {
                            // Return early if selected filter is not complete (i.e. is missing lookup field, type or value).
                            return;
                        }

                        const name = type.selectedOptions[0].value;
                        const label = field.selectedOptions[0].innerText.trim();
                        const suffix = type.selectedOptions[0].innerText.trim();
                        const text = /\(\w+\)/.test(suffix) ? `${label} ${suffix}` : label;
                        const values =
                            value?.getAttribute('multiple') !== null
                            ? [...value.selectedOptions].map((option) => ({ text: option.innerText.trim(), value: option.value }))
                            : [{ text: value?.value, value: value?.value }];

                        // If field exists in default filter form and is a single value input, use `'set'` action.
                        const action =
                            defaultFilterForm.querySelector(`[name="${name}"]`) &&
                        (value.tagName === 'INPUT' || value.tagName === 'TEXTAREA')
                            ? 'set'
                            : 'add';
                        manageDynamicFilter({ action, name, text, value: values });

                        syncDynamicToDefault(name);
                    }

                    const remove = event.target.closest('button.nb-dynamic-filter-remove');
                    if (remove) {
                        const badge = remove.closest('.badge');
                        /*
                         * Remove the group badge (i.e. multi-badge) when given badge is the group badge itself, or it is the only
                         * one left in the group (i.e. has no siblings) because removing it will leave the group badge empty anyway.
                         */
                        const shouldRemoveGroup =
                            badge?.classList.contains('nb-multi-badge') ||
                        (!badge?.previousElementSibling && !badge?.nextElementSibling);
                        shouldRemoveGroup ? badge?.closest('.nb-multi-badge')?.remove() : badge?.remove();

                        syncDynamicToDefault(badge?.querySelector('input[type="hidden"]')?.getAttribute('name'));
                    }
                });

                // Need to wrap `document` with jQuery to be able to listen to Select2 `change` event.
                $(document).on('change', (event) => {
                    const { defaultFilterForm } = getFilterForms();
                    const isDefaultFilterFormField = event.target.form === defaultFilterForm;
                    if (isDefaultFilterFormField) {
                        syncDefaultToDynamic(event.target.name);
                    }
                });

                // Need to wrap `document` with jQuery to be able to listen to Select2 `change` event.
                $(document).on('change', async (event) => {
                    const isLookupType = event.target.getAttribute('name')?.endsWith('lookup_type');
                    if (isLookupType) {
                        const content_type = event.target.getAttribute('data-contenttype');
                        const field_name = event.target.value;
                        const value = [...event.target.form.querySelectorAll('.nb-form-group')]
                            .pop()
                        ?.querySelector('input, select, textarea');

                        if (field_name) {
                            const replacement = await (async () => {
                                try {
                                    const response = await window.fetch(
                                        `/api/ui/core/filterset-fields/lookup-value-dom-element/?${new URLSearchParams({ content_type, field_name })}`,
                                        { method: 'GET', headers: { Accept: '*/*' } },
                                    );

                                    const html = await response.json();

                                    const element = document.createElement('div');
                                    element.innerHTML = html;

                                    return element.firstElementChild;
                                } catch (exception) {
                                    // Default to `<input type="text" ...>` field if error occurs.
                                    const input = document.createElement('input');
                                    input.classList.toggle('lookup_value-input', true);
                                    input.classList.toggle('form-control', true);
                                    input.setAttribute('id', `id_for_${field_name}`);
                                    input.setAttribute('name', field_name);
                                    input.setAttribute('type', 'text');
                                    return input;
                                }
                            })();

                            value.replaceWith(replacement);
                            replacement.nextElementSibling?.remove(); // Remove Select2 widget if present.
                            replacement.previousElementSibling?.setAttribute('for', replacement.getAttribute('id')); // Set label `for` attribute to match field `id`.
                            initializeInputs(replacement.parentElement);
                        }
                    }
                });

                // Need to wrap `document` with jQuery to be able to listen to Select2 `change` event.
                $(document).on('change', (event) => {
                    const isLookupField = event.target.getAttribute('name')?.endsWith('lookup_field');
                    const isLookupType = event.target.getAttribute('name')?.endsWith('lookup_type');

                    if (isLookupField) {
                        const type = event.target.form.querySelector('[name$="lookup_type"]');
                        $(type).val(null).trigger('change');
                    }

                    if (isLookupField || isLookupType) {
                        const value = [...event.target.form.querySelectorAll('.nb-form-group')]
                            .pop()
                        ?.querySelector('input, select, textarea');

                        if (value.tagName === 'SELECT') {
                            $(value).val(null).trigger('change');
                        } else {
                            value.value = '';
                        }
                    }
                });

                const isFilterForm = (target) => Boolean(target?.closest('#{{ filter_form_name }}_drawer'));

                document.addEventListener('reset', (event) => {
                    if (isFilterForm(event.target)) {
                        event.preventDefault();

                        // Remove all filter query params from location search while keeping non-filter params intact.
                        const searchParams = new URLSearchParams(window.location.search);
                        const nextSearchParams = new URLSearchParams([
                            ...[...searchParams].filter(
                                ([property]) => NON_FILTER_PARAMS.includes(property) && property !== 'all_filters_removed',
                            ),
                            ['all_filters_removed', 'true'],
                        ]);

                        location.assign(`?${nextSearchParams}`);
                    }
                });

                document.addEventListener('submit', (event) => {
                    if (isFilterForm(event.target)) {
                        event.preventDefault();

                        const { defaultFilterForm, dynamicFilterForm } = getFilterForms();

                        /*
                         * Just before the form submission automatically add filter selected in "Advanced" tab (if any)
                         * which hasn't been manually applied for whatever reason (usually forgetfulness, speed, etc.).
                         * This is a requested UX flavor.
                         */
                        const add = dynamicFilterForm.querySelector('button.nb-dynamic-filter-add');
                        add.click();

                        // Remove all field names which are not applied filters container descendants.
                        [...dynamicFilterForm.querySelectorAll('input, select, textarea')].forEach((field) =>
                            field.closest('.nb-dynamic-filter-items') ? undefined : field.removeAttribute('name'),
                        );

                        /*
                         * Get the serialized data from the forms and:
                         *   1) Filter out query params which values are empty e.g `?sam=&dan=2` becomes `dan=2`.
                         *   2) Combine the two forms into a single set of data without duplicate entries.
                         */
                        const defaultFilterFormSearchParams = new URLSearchParams(new FormData(defaultFilterForm));
                        const dynamicFilterFormSearchParams = new URLSearchParams(new FormData(dynamicFilterForm));
                        const searchParams = new URLSearchParams(window.location.search);

                        const nextSearchParams = new URLSearchParams([
                            ...[...defaultFilterFormSearchParams].filter(([, value]) => value !== ''),
                            ...[...dynamicFilterFormSearchParams].filter(
                                ([property, value]) => value !== '' && !defaultFilterFormSearchParams.has(property, value),
                            ),
                            ...[...searchParams].filter(
                                ([property]) => NON_FILTER_PARAMS.includes(property) && property !== 'all_filters_removed',
                            ),
                        ]);

                        if (nextSearchParams.has('saved_view')) {
                            const all_filters_removed = [...nextSearchParams].every(([property]) =>
                                NON_FILTER_PARAMS.includes(property),
                            );

                            if (all_filters_removed) {
                                nextSearchParams.set('all_filters_removed', 'true');
                            }
                        }

                        location.assign(`?${nextSearchParams}`);
                    }
                });

                const onSubmitOrReset = (event) => (isFilterForm(event.target) ? window.nb?.history?.saveState() : undefined);
                document.addEventListener('reset', onSubmitOrReset);
                document.addEventListener('submit', onSubmitOrReset);
            });
        })();
    </script>
{% endblock %}
